#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h> // 用于 sleep()

#define NUM_READERS 3
#define NUM_WRITERS 2

pthread_t readers[NUM_READERS], writers[NUM_WRITERS];
sem_t mutex, writeblock;
int data = 0, readers_count = 0;

void *reader(void *arg) {
    int reader_id = *(int *)arg;
    while (1) 
    {
        //多个读者线程并发访问读者线程函数的时候
        //对于读取操作可以并发执行
        //但是统计读者个数，需要一个一个执行，所以在readers_count++、--时设置为原子的
        //当有读者访问资源时，及readers_count==1时，就不能让写者访问了，所以把写者的信号量变为0
        //当读者的个数为0时，又会使写者的信号量变为1，那么在写者线程函数等待的线程就可以唤醒一个线程写入了
        sem_wait(&mutex);
        readers_count++;
        if (readers_count == 1) {
            sem_wait(&writeblock);
        }
        sem_post(&mutex);

        // 读操作
        printf("读者 %d 读取数据: %d\n", reader_id, data);
        
        sem_wait(&mutex);
        readers_count--;
        if (readers_count == 0) {
            sem_post(&writeblock);
        }
        sem_post(&mutex);

        sleep(1); // 模拟读取时间
    }
    pthread_exit(NULL);
}

void *writer(void *arg) {
    int writer_id = *(int *)arg;
    while (1) 
    {
        //与读者不一样的是
        sem_wait(&writeblock);
        data++;
        printf("写者 %d 写入数据: %d\n", writer_id, data);
        sem_post(&writeblock);

        sleep(2); // 模拟写入时间
    }
    pthread_exit(NULL);
}

int main() {
    int i;
    int reader_ids[NUM_READERS], writer_ids[NUM_WRITERS];

    sem_init(&mutex, 0, 1);
    sem_init(&writeblock, 0, 1);

    // 创建读者线程3
    for (i = 0; i < NUM_READERS; i++) {
        reader_ids[i] = i + 1;
        pthread_create(&readers[i], NULL, reader, &reader_ids[i]);
    }

    // 创建写者线程2
    for (i = 0; i < NUM_WRITERS; i++) {
        writer_ids[i] = i + 1;
        pthread_create(&writers[i], NULL, writer, &writer_ids[i]);
    }

    // 等待所有读者线程结束
    for (i = 0; i < NUM_READERS; i++) {
        pthread_join(readers[i], NULL);
    }

    // 等待所有写者线程结束
    for (i = 0; i < NUM_WRITERS; i++) {
        pthread_join(writers[i], NULL);
    }

    sem_destroy(&mutex);
    sem_destroy(&writeblock);

    return 0;
}
